/**
 * Copyright (c), 上海沃恩信息科技有限公司.
 * All rights reserved.
 * Licensed under BSD (https://www.pittypat.work/bsd.html).
 */

package pittypat;

import java.io.IOException;
import java.io.Writer;
import java.util.Base64;

/**
 * 提供将值编写为 JSON 字符串并写入目标 Writer 的辅助方法，以及将值转换为 IJson 的方法。 Pittypat 约定 API
 * 方法必须返回一个 IJson 对象，使用 Json.from 系列方法将基础类型转换为 IJson 对象。
 */
public final class Json {
	private Json() {
	}

	/**
	 * 将指定的字符串按 JSON 要求进行转义或编码。不会在前后添加 "" 双引号。
	 * 
	 * @param s
	 *            目标字符串。
	 * @return 转义或编码后的符合 JSON 要求的字符串。
	 */
	public static String escape(String s) {
		if (s == null || s.length() == 0) {
			return "";
		}

		StringBuilder b = null;
		int startIndex = 0;
		int count = 0;
		char c = '\0';
		int len = s.length();
		String t = "";

		for (int i = 0; i < len; i++) {
			c = s.charAt(i);

			if (c < 0x20 || c == '\"' || c == '\\' || c == '/') {
				if (b == null) {
					b = new StringBuilder(len + 5);
				}

				if (count > 0) {
					b.append(s, startIndex, startIndex + count);
				}

				startIndex = i + 1;
				count = 0;
			}

			switch (c) {
			case '\"':
				b.append("\\\"");
				break;
			case '\\':
				b.append("\\\\");
				break;
			case '/':
				b.append("\\/");
				break;
			case '\r':
				b.append("\\r");
				break;
			case '\t':
				b.append("\\t");
				break;
			case '\n':
				b.append("\\n");
				break;
			case '\b':
				b.append("\\b");
				break;
			case '\f':
				b.append("\\f");
				break;
			default:
				if (c < 0x20) {
					b.append("\\u");
					t = "000" + Integer.toHexString((int) c);
					b.append(t.substring(t.length() - 4));
				} else {
					++count;
				}
				break;
			}
		}

		if (b == null) {
			return s;
		}

		if (count > 0) {
			b.append(s, startIndex, startIndex + count);
		}

		return b.toString();
	}

	/**
	 * 获取指定值的类型名称。 此方法返回的字符串不是 Java 中类型的字符串，而是自定义的类型字符串，以便返回的字符串与 C#
	 * 中返回的类型字符串是一致。
	 * 
	 * @param value
	 *            目标值。
	 * @return 类型名称字符串，如果 value 为 null，则返回字符串 "null"。
	 */
	public static String getTypeName(IJson value) {
		if (value == null) {
			return "null";
		}

		Class<?> type = value.getClass();

		if (type == pittypat.primitives.String.class) {
			return "string";
		}

		if (type == pittypat.primitives.Int.class) {
			return "int32";
		}

		if (type == pittypat.primitives.Boolean.class) {
			return "bool";
		}

		if (type == pittypat.primitives.DateTime.class) {
			return "datetime";
		}

		if (type == pittypat.primitives.Uuid.class) {
			return "uuid";
		}

		if (type == pittypat.primitives.Binary.class) {
			return "binary";
		}

		if (type == pittypat.primitives.Date.class) {
			return "date";
		}

		if (type == pittypat.primitives.Decimal.class) {
			return "decimal";
		}

		if (type == pittypat.primitives.Float.class) {
			return "float32";
		}

		if (type == pittypat.primitives.Double.class) {
			return "float64";
		}

		if (type == pittypat.primitives.Short.class) {
			return "int16";
		}

		if (type == pittypat.primitives.Long.class) {
			return "int64";
		}

		if (type == pittypat.primitives.Time.class) {
			return "time";
		}

		if (type == NotificationList.class) {
			return "notificationlist";
		}

		if (type == NavList.class) {
			return "navlist";
		}

		if (type == DynamicModel.class) {
			return "dynamicmodel";
		}

		if (IUser.class.isAssignableFrom(type)) {
			return "user";
		}

		if (INotification.class.isAssignableFrom(type)) {
			return "notification";
		}

		if (INavApp.class.isAssignableFrom(type)) {
			return "navapp";
		}

		if (INavModule.class.isAssignableFrom(type)) {
			return "navmodule";
		}

		if (DataModel.class.isAssignableFrom(type) || FunctionArgs.class.isAssignableFrom(type)) {
			return (type.getPackage().getName().substring(4) + "." + type.getName()).toLowerCase();
		}

		if (FunctionModel.class.isAssignableFrom(type)) {
			return "functionmodel";
		}

		if (DataList.class.isAssignableFrom(type)) {
			Class<?> itemType = ((DataList<?>) value).getItemType();
			return "list<" + ((itemType.getPackage().getName().substring(4) + "." + itemType.getName()).toLowerCase())
					+ ">";
		}

		return type.getName().toLowerCase();
	}

	/**
	 * 将指定的值转换为 JSON 字符串写入目标文本流。
	 * 
	 * @param writer
	 *            目标文本流。
	 * @param value
	 *            要写入的值。
	 * @throws IOException
	 */
	public static void write(Writer writer, java.lang.Object value) throws IOException {
		if (value == null) {
			writer.write("null");
		} else if (value instanceof Boolean) {
			write(writer, (Boolean) value);
		} else if (value instanceof java.util.UUID) {
			write(writer, (java.util.UUID) value);
		} else if (value instanceof String) {
			write(writer, (String) value);
		} else if (value instanceof byte[]) {
			write(writer, (byte[]) value);
		} else if (value instanceof Byte) {
			write(writer, (Byte) value);
		} else if (value instanceof Short) {
			write(writer, (Short) value);
		} else if (value instanceof Integer) {
			write(writer, (Integer) value);
		} else if (value instanceof Long) {
			write(writer, (Long) value);
		} else if (value instanceof Float) {
			write(writer, (Float) value);
		} else if (value instanceof Double) {
			write(writer, (Double) value);
		} else if (value instanceof java.math.BigDecimal) {
			write(writer, (java.math.BigDecimal) value);
		} else if (value instanceof java.time.LocalDateTime) {
			write(writer, (java.time.LocalDateTime) value);
		} else if (value instanceof java.time.LocalDate) {
			write(writer, (java.time.LocalDate) value);
		} else if (value instanceof java.time.LocalTime) {
			write(writer, (java.time.LocalTime) value);
		} else if (value instanceof IJson) {
			write(writer, (IJson) value);
		}
	}

	/**
	 * 将指定的值转换为 JSON 字符串写入目标文本流。
	 * 
	 * @param writer
	 *            目标文本流。
	 * @param value
	 *            要写入的值。
	 * @throws IOException
	 */
	public static void write(Writer writer, java.lang.Boolean value) throws IOException {
		if (value == null) {
			writer.write("null");
		} else if (value == true) {
			writer.write("true");
		} else {
			writer.write("false");
		}
	}

	/**
	 * 将指定的值转换为 JSON 字符串写入目标文本流。
	 * <p>
	 * 将 UUID 转换为对 URL 友好的 BASE64 写入目标流。
	 * 
	 * @param writer
	 *            目标文本流。
	 * @param value
	 *            要写入的值。
	 */
	public static void write(Writer writer, java.util.UUID value) throws IOException {
		if (value == null) {
			writer.write("null");
		} else {
			writer.write("\"" + pittypat.primitives.Uuid.toBase64(value) + "\"");
		}
	}

	/**
	 * 将指定的值转换为 JSON 字符串写入目标文本流。
	 * 
	 * @param writer
	 *            目标文本流。
	 * @param value
	 *            要写入的值。
	 */
	public static void write(Writer writer, java.lang.String value) throws IOException {
		if (value == null) {
			writer.write("null");
		} else {
			writer.write('"');
			writer.write(escape(value));
			writer.write('"');
		}
	}

	/**
	 * 将指定的值转换为 JSON 字符串写入目标文本流。
	 * <p>
	 * 字节数组被转换为 BASE64 然后写入目标流。
	 * 
	 * @param writer
	 *            目标文本流。
	 * @param value
	 *            要写入的值。
	 */
	public static void write(Writer writer, byte[] value) throws IOException {
		if (value == null) {
			writer.write("null");
		} else if (value.length == 0) {
			writer.write("\"\"");
		} else {
			writer.write("\"" + escape(Base64.getEncoder().encodeToString(value)) + "\"");
		}
	}

	/**
	 * 将指定的值转换为 JSON 字符串写入目标文本流。
	 * 
	 * @param writer
	 *            目标文本流。
	 * @param value
	 *            要写入的值。
	 */
	public static void write(Writer writer, java.lang.Byte value) throws IOException {
		if (value == null) {
			writer.write("null");
		} else {
			writer.write(value.toString());
		}
	}

	/**
	 * 将指定的值转换为 JSON 字符串写入目标文本流。
	 * 
	 * @param writer
	 *            目标文本流。
	 * @param value
	 *            要写入的值。
	 */
	public static void write(Writer writer, java.lang.Short value) throws IOException {
		if (value == null) {
			writer.write("null");
		} else {
			writer.write(value.toString());
		}
	}

	/**
	 * 将指定的值转换为 JSON 字符串写入目标文本流。
	 * 
	 * @param writer
	 *            目标文本流。
	 * @param value
	 *            要写入的值。
	 */
	public static void write(Writer writer, java.lang.Integer value) throws IOException {
		if (value == null) {
			writer.write("null");
		} else {
			writer.write(value.toString());
		}
	}

	/**
	 * 将指定的值转换为 JSON 字符串写入目标文本流。
	 * <p>
	 * 由于 JavaScript 无法精确超过 53 位的整数，因此总是将 64 位整数转换为字符串，然后写入目标流。
	 * 
	 * @param writer
	 *            目标文本流。
	 * @param value
	 *            要写入的值。
	 */
	public static void write(Writer writer, java.lang.Long value) throws IOException {
		if (value == null) {
			writer.write("null");
		} else {
			writer.write("\"" + value.toString() + "\"");
		}
	}

	/**
	 * 将指定的值转换为 JSON 字符串写入目标文本流。
	 * 
	 * @param writer
	 *            目标文本流。
	 * @param value
	 *            要写入的值。
	 */
	public static void write(Writer writer, java.math.BigDecimal value) throws IOException {
		if (value == null) {
			writer.write("null");
		} else {
			writer.write(value.toString());
		}
	}

	/**
	 * 将指定的值转换为 JSON 字符串写入目标文本流。
	 * 
	 * @param writer
	 *            目标文本流。
	 * @param value
	 *            要写入的值。
	 */
	public static void write(Writer writer, java.lang.Float value) throws IOException {
		if (value == null) {
			writer.write("null");
		} else {
			writer.write(value.toString());
		}
	}

	/**
	 * 将指定的值转换为 JSON 字符串写入目标文本流。
	 * 
	 * @param writer
	 *            目标文本流。
	 * @param value
	 *            要写入的值。
	 */
	public static void write(Writer writer, java.lang.Double value) throws IOException {
		if (value == null) {
			writer.write("null");
		} else {
			writer.write(value.toString());
		}
	}

	/**
	 * 将指定的值转换为 JSON 字符串写入目标文本流。
	 * 
	 * @param writer
	 *            目标文本流。
	 * @param value
	 *            要写入的值。
	 */
	public static void write(Writer writer, java.time.LocalDateTime value) throws IOException {
		if (value == null) {
			writer.write("null");
		} else {
			writer.write("{\"year\":" + value.getYear() + ",\"month\":" + value.getMonthValue() + ",\"day\":"
					+ value.getDayOfMonth() + ",\"hour\":" + value.getHour() + ",\"minute\":" + value.getMinute()
					+ ",\"second\":" + value.getSecond() + ",\"millisecond\":" + (int) (value.getNano() / 1000000)
					+ "}");
		}
	}

	/**
	 * 将指定的值转换为 JSON 字符串写入目标文本流。
	 * 
	 * @param writer
	 *            目标文本流。
	 * @param value
	 *            要写入的值。
	 */
	public static void write(Writer writer, java.time.LocalDate value) throws IOException {
		if (value == null) {
			writer.write("null");
		} else {
			writer.write("{\"year\":" + value.getYear() + ",\"month\":" + value.getMonthValue() + ",\"day\":"
					+ value.getDayOfMonth() + "}");
		}
	}

	/**
	 * 将指定的值转换为 JSON 字符串写入目标文本流。
	 * 
	 * @param writer
	 *            目标文本流。
	 * @param value
	 *            要写入的值。
	 */
	public static void write(Writer writer, java.time.LocalTime value) throws IOException {
		if (value == null) {
			writer.write("null");
		} else {
			writer.write("{\"hour\":" + value.getHour() + ",\"minute\":" + value.getMinute() + ",\"second\":"
					+ value.getSecond() + ",\"millisecond\":" + (int) (value.getNano() / 1000000) + "}");
		}
	}

	/**
	 * 将指定的值转换为 JSON 字符串写入目标文本流。
	 * 
	 * @param writer
	 *            目标文本流。
	 * @param value
	 *            要写入的值。
	 */
	public static void write(Writer writer, IJson value) throws IOException {
		if (value == null) {
			writer.write("null");
		} else {
			value.write(writer);
		}
	}

	/**
	 * 将指定的值转换为一个 IJson 对象。
	 * 
	 * @param value
	 *            要转换的值。
	 */
	public static IJson from(java.lang.Boolean value) {
		return value == null ? null : new pittypat.primitives.Boolean(value);
	}

	/**
	 * 将指定的值转换为一个 IJson 对象。
	 * 
	 * @param value
	 *            要转换的值。
	 */
	public static IJson from(java.util.UUID value) {
		return value == null ? null : new pittypat.primitives.Uuid(value);
	}

	/**
	 * 将指定的值转换为一个 IJson 对象。
	 * 
	 * @param value
	 *            要转换的值。
	 */
	public static IJson from(java.lang.String value) {
		return value == null ? null : new pittypat.primitives.String(value);
	}

	/**
	 * 将指定的值转换为一个 IJson 对象。
	 * 
	 * @param value
	 *            要转换的值。
	 */
	public static IJson from(byte[] value) {
		return value == null ? null : new pittypat.primitives.Binary(value);
	}

	/**
	 * 将指定的值转换为一个 IJson 对象。
	 * 
	 * @param value
	 *            要转换的值。
	 */
	public static IJson from(java.lang.Byte value) {
		return value == null ? null : new pittypat.primitives.Byte(value);
	}

	/**
	 * 将指定的值转换为一个 IJson 对象。
	 * 
	 * @param value
	 *            要转换的值。
	 */
	public static IJson from(java.lang.Short value) {
		return value == null ? null : new pittypat.primitives.Short(value);
	}

	/**
	 * 将指定的值转换为一个 IJson 对象。
	 * 
	 * @param value
	 *            要转换的值。
	 */
	public static IJson from(java.lang.Integer value) {
		return value == null ? null : new pittypat.primitives.Int(value);
	}

	/**
	 * 将指定的值转换为一个 IJson 对象。
	 * 
	 * @param value
	 *            要转换的值。
	 */
	public static IJson from(java.lang.Long value) {
		return value == null ? null : new pittypat.primitives.Long(value);
	}

	/**
	 * 将指定的值转换为一个 IJson 对象。
	 * 
	 * @param value
	 *            要转换的值。
	 */
	public static IJson from(java.math.BigDecimal value) {
		return value == null ? null : new pittypat.primitives.Decimal(value);
	}

	/**
	 * 将指定的值转换为一个 IJson 对象。
	 * 
	 * @param value
	 *            要转换的值。
	 */
	public static IJson from(java.lang.Float value) {
		return value == null ? null : new pittypat.primitives.Float(value);
	}

	/**
	 * 将指定的值转换为一个 IJson 对象。
	 * 
	 * @param value
	 *            要转换的值。
	 */
	public static IJson from(java.lang.Double value) {
		return value == null ? null : new pittypat.primitives.Double(value);
	}

	/**
	 * 将指定的值转换为一个 IJson 对象。
	 * 
	 * @param value
	 *            要转换的值。
	 */
	public static IJson from(java.time.LocalDateTime value) {
		return value == null ? null : new pittypat.primitives.DateTime(value);
	}

	/**
	 * 将指定的值转换为一个 IJson 对象。
	 * 
	 * @param value
	 *            要转换的值。
	 */
	public static IJson from(java.time.LocalDate value) {
		return value == null ? null : new pittypat.primitives.Date(value);
	}

	/**
	 * 将指定的值转换为一个 IJson 对象。
	 * 
	 * @param value
	 *            要转换的值。
	 */
	public static IJson from(java.time.LocalTime value) {
		return value == null ? null : new pittypat.primitives.Time(value);
	}

	/**
	 * 将指定的值转换为一个 IJson 对象。
	 * 
	 * @param value
	 *            要转换的值。
	 */
	public static IJson from(IJson value) {
		return value;
	}
}
